package com.isesol.arch.web.servlets;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.isesol.arch.common.utils.PropertyFileUtil;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * classpath下面的属性配置文件读取初始化类
 *
 */
public class PropertiesServlet extends HttpServlet {

    private static final long serialVersionUID = 1L;
    protected Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * @see HttpServlet#HttpServlet()
     */
    public PropertiesServlet() {
        super();
    }

    /**
     * @see Servlet#init(ServletConfig)
     */
    public void init(ServletConfig config) throws ServletException {
        try {
            String profile = config.getInitParameter("profile");
            if (PropertyFileUtil.initialized) {
                logger.info("---- 已初始化，忽略本次初始化操作 ----");
                setParameterToServerContext(config.getServletContext());
                return;
            }
            if (StringUtils.isNotBlank(profile)) {
                logger.info("启用特定Profile=" + profile);
                PropertyFileUtil.init(profile);
            } else {
                PropertyFileUtil.init();
                logger.info("启用默认Profile");
            }
            setParameterToServerContext(config.getServletContext());
            logger.info("++++ 初始化[classpath下面的属性配置文件]完成 ++++");
        } catch (IOException e) {
            logger.error("初始化classpath下的属性文件失败", e);
        }
    }

    /**
     * 绑定参数到ServletContext
     *
     * @param servletContext
     */
    private void setParameterToServerContext(ServletContext servletContext) {
        servletContext.setAttribute("prop", PropertyFileUtil.getKeyValueMap());
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String action = StringUtils.defaultString(req.getParameter("action"));
        String format = StringUtils.defaultString(req.getParameter("format"), "html");

        if (StringUtils.isNotBlank(format) && format.equals("json")) {

            resp.setContentType("application/json");

        } else if (StringUtils.isNotBlank(format) && format.equals("html")) {

            resp.setContentType("text/plain;charset=UTF-8");
        }

        String token = req.getParameter("token");
        if (!StringUtils.equals(token, PropertyFileUtil.get("system.action.private.key"))) {
            resp.getWriter().print("reject request, token is invalid.");
            return;
        }

        if ("reload".equals(action)) { // 重载
            try {
                String profile = StringUtils.defaultString(req.getParameter("profile"), PropertyFileUtil.getProfile());
                if (StringUtils.isNotBlank(profile)) {
                    logger.info("重载配置，使用特定Profile=" + profile);
                }
                PropertyFileUtil.init(profile);

                setParameterToServerContext(req.getSession().getServletContext());

                writeProperties(resp, format);
                logger.info("++++ 已完成属性配置文件重载任务 ++++，{IP={}}", req.getRemoteAddr());
            } catch (IOException e) {
                logger.error("重载属性文件失败", e);
            }
        } else if ("getprop".equals(action)) { // 获取属性
            String key = StringUtils.defaultString(req.getParameter("key"));
            resp.getWriter().print(ObjectUtils.toString(PropertyFileUtil.get(key)));
        } else if ("list-all".equals(action)) { // 获取全部属性
            writeProperties(resp, format);
        } else if ("list-split".equals(action)) { // 分文件获取全部属性
            writePropertiesBySplit(resp, format);
        } else if ("files".equals(action)) {
            writeActiveFiles(resp);
        } else if ("save".equals(action)) {
            String parameterName = StringUtils.defaultString(req.getParameter("parameterName"));
            String parameterValue = StringUtils.defaultString(req.getParameter("parameterValue"));
            saveParameter(parameterName, parameterValue, resp);
        } else if ("delete".equals(action)) {
            String parameterKey = StringUtils.defaultString(req.getParameter("parameterKey"));
            deleteParameter(parameterKey, resp);
        } else if ("equals".equals(action)) { // 比较属性
            String key = StringUtils.defaultString(req.getParameter("key"));
            String diffValue = StringUtils.defaultString(req.getParameter("value"));
            String propValue = PropertyFileUtil.get(key);
            resp.getWriter().print(StringUtils.equals(propValue, diffValue));
        } else {
            writeMain(req, resp);
        }
    }

    /**
     * 新增/修改本地配置文件的属性
     *
     * @param parameterName
     * @param parameterValue
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    public void saveParameter(String parameterName, String parameterValue, HttpServletResponse resp) throws ServletException, IOException {

        InputStream inputStream = null;
        InputStream cInputStream = null;
        OutputStream out = null;
        try {
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            inputStream = loader.getResourceAsStream("application-files.properties");

            BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
            boolean isFile = false;
            String lastLine = "";
            String sCurrentLine;
            while ((sCurrentLine = in.readLine()) != null) {

                if (StringUtils.isNotBlank(sCurrentLine)) {
                    sCurrentLine = sCurrentLine.substring(sCurrentLine.indexOf("=") + 1);
                    if (null != sCurrentLine && sCurrentLine.startsWith("file:")) {
                        sCurrentLine = sCurrentLine.substring(sCurrentLine.indexOf(":") + 1);
                        File f = new File(sCurrentLine);

                        if (f.exists()) {
                            lastLine = sCurrentLine;
                            isFile = true;
                        }
                    } else {
                        isFile = false;
                    }
                }
            }
            lastLine = lastLine.substring(lastLine.indexOf("=") + 1);
            if (isFile) {
                Properties props = new Properties();
                props.load(inputStream);

                File file = new File(lastLine);
                if (file.getParentFile() != null && !file.getParentFile().exists()) {
                    file.getParentFile().mkdirs();
                }
                if (!file.exists()) {
                    file.createNewFile();
                }
                Properties cProps = new Properties();
                cInputStream = new FileInputStream(file);
                cProps.load(cInputStream);
                out = new FileOutputStream(lastLine);
                cProps.setProperty(parameterName, parameterValue);
                cProps.store(out, "Update:'" + parameterName + "' value:" + parameterValue);
                out.flush();
                out.close();
                cInputStream.close();
                inputStream.close();
                PropertyFileUtil.init();
                resp.getWriter().print("success");
            }

        } catch (Exception e) {
            logger.error("新增/修改属性:", e);
            resp.getWriter().print("error:" + e.getMessage());
        } finally {
            if (null != inputStream) {
                inputStream.close();
            }
            if (null != cInputStream) {
                cInputStream.close();
            }
            if (null != out) {
                out.close();
            }
        }
    }

    /**
     * 删除本地配置文件的属性
     *
     * @param parameterKey
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    public void deleteParameter(String parameterKey, HttpServletResponse resp) throws ServletException, IOException {

        InputStream inputStream = null;
        InputStream cInputStream = null;
        OutputStream out = null;
        try {
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            inputStream = loader.getResourceAsStream("application-files.properties");
            BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
            String lastLine = "";
            String sCurrentLine;
            boolean isFile = false;
            while ((sCurrentLine = in.readLine()) != null) {

                if (StringUtils.isNotBlank(sCurrentLine)) {
                    sCurrentLine = sCurrentLine.substring(sCurrentLine.indexOf("=") + 1);
                    if (null != sCurrentLine && sCurrentLine.startsWith("file:")) {
                        sCurrentLine = sCurrentLine.substring(sCurrentLine.indexOf(":") + 1);
                        File f = new File(sCurrentLine);

                        if (f.exists()) {
                            lastLine = sCurrentLine;
                            isFile = true;
                        }
                    } else {
                        isFile = false;
                    }
                }
            }
            if (isFile) {
                Properties props = new Properties();
                props.load(inputStream);
                lastLine = lastLine.substring(lastLine.indexOf("=") + 1);
                File file = new File(lastLine);
                if (file.getParentFile() != null && !file.getParentFile().exists()) {
                    file.getParentFile().mkdirs();
                }
                if (!file.exists()) {
                    file.createNewFile();
                }
                Properties cProps = new Properties();
                cInputStream = new FileInputStream(file);
                cProps.load(cInputStream);
                cProps.remove(parameterKey);
                out = new FileOutputStream(lastLine);
                cProps.store(out, "delete:'" + parameterKey);
                out.flush();
                out.close();
                cInputStream.close();
                inputStream.close();
                PropertyFileUtil.init();
                resp.getWriter().print("success");
            }

        } catch (Exception e) {
            logger.error("删除本地配置文件的属性:", e);
            resp.getWriter().print("error:" + e.getMessage());
        } finally {
            if (null != inputStream) {
                inputStream.close();
            }
            if (null != cInputStream) {
                cInputStream.close();
            }
            if (null != out) {
                out.close();
            }
        }
    }

    /**
     * 输出属性以及值列表到页面
     *
     * @param resp
     * @throws IOException
     */
    protected void writeProperties(HttpServletResponse resp, String format) throws IOException {
        Set<String> keys = PropertyFileUtil.getKeys();
        StringBuilder sb = new StringBuilder();
        if (StringUtils.isNotBlank(format) && format.equals("json")) {
            Map propMap = Maps.newHashMap();
            resp.setContentType("application/json");

            for (Object key : keys) {
                propMap.put(key, PropertyFileUtil.get(key.toString()));
            }

            resp.getWriter().print(JSON.toJSONString(propMap, true));
        } else if (StringUtils.isNotBlank(format) && format.equals("html")) {
            resp.setContentType("text/html");

            resp.getWriter().print("<b>属性文件重载成功！</b><br/>");

            wrapToHtml(keys, sb);
            resp.getWriter().print("<html><body>" + sb.toString() + "</body></html>");


        }

    }

    private void wrapToHtml(Set<? extends Object> keys, StringBuilder sb) {
        String baseStyle = "border-bottom: 1px solid #ddd; border-right: 1px solid #ddd; padding: 0.5em";
        sb.append("<table>");
        sb.append("<thead>");
        sb.append("<tr>");
        sb.append("<th style='" + baseStyle + "'>Index</th>");
        sb.append("<th style='" + baseStyle + "'>Key</th>");
        sb.append("<th style='" + baseStyle + "'>Value</th>");
        sb.append("</thead>");
        sb.append("<tbody>");
        int count = 1;
        for (Object key : keys) {
            sb.append("<tr><td style='" + baseStyle + "'>" + count++ + "</td><td style='color:red;font-weight:bold;" + baseStyle + "'>" + key + "</td><td style='" + baseStyle + "'>" + PropertyFileUtil.get(key.toString()) + "</td></tr>");
        }
        sb.append("</tbody>");
        sb.append("</table>");
    }

    /**
     * 分文件获取全部属性
     *
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    protected void writePropertiesBySplit(HttpServletResponse resp, String format) throws ServletException, IOException {
        InputStream inputStream = null;
        InputStream cInputStream = null;
        StringBuilder sb = new StringBuilder();
        try {
            ClassLoader loader = Thread.currentThread().getContextClassLoader();
            inputStream = loader.getResourceAsStream("application-files.properties");
            Properties props = new Properties();
            props.load(inputStream);
            Set<Object> fileKeySet = props.keySet();
            Map propMap = Maps.newHashMap();
            if (StringUtils.isNotBlank(format) && format.equals("json")) {
                for (Object obj : fileKeySet) {

                    if (props.getProperty(obj.toString()).split(":").length > 1) {
                        File file = new File(props.getProperty(obj.toString()).split(":")[1]);
                        if (file.getParentFile() != null && !file.getParentFile().exists()) {
                            continue;
                        }
                        if (!file.exists()) {
                            continue;
                        }
                        cInputStream = new FileInputStream(file);
                    } else {
                        cInputStream = loader.getResourceAsStream(props.getProperty(obj.toString()));
                    }
                    Properties specialProps = new Properties();
                    specialProps.load(cInputStream);
                    Set<Object> cFileKeySet = specialProps.keySet();

                    Map map = Maps.newHashMap();
                    for (Object key : cFileKeySet) {
                        map.put(key, PropertyFileUtil.get(key.toString()));
                    }

                    cInputStream.close();

                    propMap.put(props.getProperty(obj.toString()), map);
                }
                sb.append(JSON.toJSONString(propMap, true));
                inputStream.close();
                resp.setContentType("application/json");
                resp.getWriter().print(sb.toString());

            } else if (StringUtils.isNotBlank(format) && format.equals("html")) {
                for (Object obj : fileKeySet) {
                    sb.append("<span style='color:red;font-weight:bold;'>" + props.getProperty(obj.toString()) + "</span><br/>");
                    if (props.getProperty(obj.toString()).split(":").length > 1) {
                        File file = new File(props.getProperty(obj.toString()).split(":")[1]);
                        if (file.getParentFile() != null && !file.getParentFile().exists()) {
                            continue;
                        }
                        if (!file.exists()) {
                            continue;
                        }
                        cInputStream = new FileInputStream(file);
                    } else {
                        cInputStream = loader.getResourceAsStream(props.getProperty(obj.toString()));
                    }
                    Properties specialProps = new Properties();
                    specialProps.load(cInputStream);
                    Set<Object> cFileKeySet = specialProps.keySet();
                    wrapToHtml(cFileKeySet, sb);
                    cInputStream.close();
                }

                inputStream.close();
                resp.setContentType("text/html");
                resp.getWriter().print("<html><body>" + sb.toString() + "</body></html>");
            }

        } catch (Exception e) {
            logger.error("删除本地配置文件的属性:", e);
            resp.getWriter().print("error:" + e.getMessage());
        } finally {
            if (null != inputStream) {
                inputStream.close();
            }
            if (null != cInputStream) {
                cInputStream.close();
            }
        }
    }

    /**
     * 输出启用的配置文件列表到页面
     *
     * @param resp
     * @throws IOException
     */
    protected void writeActiveFiles(HttpServletResponse resp) throws IOException {
        Properties activePropertyFiles = PropertyFileUtil.getActivePropertyFiles();
        Enumeration<Object> keys = activePropertyFiles.keys();
        StringBuilder sb = new StringBuilder();
        while (keys.hasMoreElements()) {
            String key = keys.nextElement().toString();
            sb.append(key + "<span style='color:red;font-weight:bold;'>=</span>" + activePropertyFiles.get(key) + "<br/>");
        }
        resp.setContentType("text/html");
        resp.getWriter().print("<html><body><h4>依次读取以下配置文件（Profile=" + PropertyFileUtil.getProfile() + "）：</h4>" + sb.toString() + "</body></html>");
    }

    protected void writeMain(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        resp.setContentType("text/html");
        String contextPath = req.getContextPath();
        PrintWriter pw = resp.getWriter();
        String elementformat = "<li><a href='" + contextPath + "/servlet/properties?token=" + StringUtils.defaultString(req.getParameter("token")) + "&action=%1s' target='_blank'>%2s</a></li>";
        pw.println("<ul>");
        pw.println(String.format(elementformat, "files", "属性文件列表"));
        pw.println(String.format(elementformat, "list-all", "所有属性列表"));
        pw.println(String.format(elementformat, "list-split", "属性列表（分割显示）"));
        pw.println(String.format(elementformat, "reload", "重新加载"));
        pw.println(String.format(elementformat, "getprop&key=sample", "获取属性"));
        pw.println("</ul>");
    }
}